﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;

namespace Titan.Reflection
{
    internal static partial class ObjectInvoker
    {
        public interface IObjectInvoker
        {
            object Invoke(object target, object[] args);
        }

        public static object Invoke(MethodInfo method, object target, object[] args)
        {
            IObjectInvoker invoker = GetInvoker(method);

            if (null != invoker)
                return invoker.Invoke(target, args);
            else
                return method.Invoke(target, args);
        }

        public static IObjectInvoker GetInvoker(MethodInfo method)
        {
            IntPtr ptr = method.MethodHandle.Value;
            IObjectInvoker invoker;

            //如果代码频繁尝试访问字典中不存在的键，则可使用 TryGetValue 方法
            //TryGetValue:如果未找到键，value 参数便为值类型 TValue 获取适当的默认值；
            //例如，为 integer 类型获取 0（零），为布尔值类型获取 false，为引用类型获取 null 引用（在 Visual Basic 中为 Nothing）。
            bool contains = _methodMap.TryGetValue(ptr, out invoker);
            if (!contains)
            {
                lock (_methodMap)
                {
                    contains = _methodMap.TryGetValue(ptr, out invoker);
                    if (!contains)
                    {
                        invoker = CreateInvoker(method);
                        _methodMap.Add(ptr, invoker);
                    }
                }
            }
            return invoker;
        }

        private static IObjectInvoker CreateInvoker(MethodInfo method)
        {
            IObjectInvoker invoker = null;

            bool isProcedure = (method.ReturnType == typeof(void));
            ParameterInfo[] parameters = method.GetParameters();

            Type type = GetInvokerType(parameters, isProcedure);

            if (null != type)
            {

                List<Type> typeArgs = new List<Type>();

                typeArgs.Add(method.DeclaringType);

                if (!isProcedure)
                    typeArgs.Add(method.ReturnType);

                for (int i = 0; i < parameters.Length; i++)
                    typeArgs.Add(parameters[i].ParameterType);

                Type invokerType = type.MakeGenericType(typeArgs.ToArray());

                invoker = (IObjectInvoker)invokerType.InvokeMember(
                    null,
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
                    null,
                    null,
                    new object[] { method });
            }

            return invoker;
        }

        private static Type GetInvokerType(ParameterInfo[] parameters, bool isProcedure)
        {
            string typeName = isProcedure ? "__Procedure" : "__Function";

            for (int i = 0; i < parameters.Length; i++)
            {
                ParameterDirection direction = Binder.Default.GetDirection(parameters[i]);
                switch (direction)
                {
                    case ParameterDirection.Input:
                        typeName += "I";
                        break;
                    case ParameterDirection.Output:
                        typeName += "O";
                        break;
                    case ParameterDirection.InputOutput:
                        typeName += "R";
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(); //impossible
                }
            }
            int typeArgCount = 1; //<T,...>

            if (!isProcedure)
                typeArgCount++; //<T,ReturnT,...>

            typeArgCount += parameters.Length; //<..., Arg1T, Arg2T, ...>

            typeName = typeName + "`" + typeArgCount;

            return typeof(ObjectInvoker).GetNestedType(typeName, BindingFlags.Public | BindingFlags.NonPublic);
        }


        private static Dictionary<IntPtr, IObjectInvoker> _methodMap = new Dictionary<IntPtr, IObjectInvoker>();

    }
}
